package com.example.bbgds.test;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;

/**
 * ExpandView
 *
 * @author zjzhu
 * @date Mar 27,2015
 */
@SuppressLint({"RtlHardcoded", "ClickableViewAccessibility"})
public class ExpandView extends FrameLayout implements View.OnLayoutChangeListener {

    private static final int[] LAYOUT_ATTRS = new int[]{android.R.attr.layout_gravity};
    private static final int MIN_FLING_VELOCITY = 1500;

    private static final float EXPAND_THRESHOLD = 0.5f;
    private static final float COLLAPSE_THRESHOLD = 0.25f;

    private static final long AUTOMATIC_COLLAPSE_DURATION = 500;
    private static final long EXPAND_DURATION = 200;
    private static final int EXPAND_EDGE_SIZE = 30;

    private static final int TOUCH_STATE_REST = 0;
    private static final int TOUCH_STATE_SCROLLING = 1;

    private int mTouchState = TOUCH_STATE_REST;
    private float mLastMotionX, mLastMotionY;
    private VelocityTracker mVelocityTracker;

    private int mGravity = Gravity.NO_GRAVITY;
    private int mCollapseEdgeSize;
    private int mExpandEdgeSize;
    private boolean mIsExpanded = false;
    private boolean mNextExpandedStatus = false;
    private boolean mTranslationFinished = true;

    private ExpandListener mExpandListener = null;
    private float mMinVelocity;
    private int mTouchSlop;
    private boolean mHorizontalReverseExit = false;

    public ExpandView(Context context) {
        this(context, null);
    }

    public ExpandView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ExpandView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
        mGravity = a.getInt(0, Gravity.NO_GRAVITY);
        a.recycle();
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        final float density = context.getResources().getDisplayMetrics().density;
        mCollapseEdgeSize = context.getResources().getDisplayMetrics().widthPixels;//(int) (COLLAPSE_EDGE_SIZE * density);
        mExpandEdgeSize = (int) (EXPAND_EDGE_SIZE * density);
        mMinVelocity = MIN_FLING_VELOCITY * density;

        addOnLayoutChangeListener(this);
    }

    public void setExpandListener(ExpandListener listener) {
        mExpandListener = listener;
    }

    public float getExpandMinVelocity() {
        return mMinVelocity;
    }

    public long getExpandAnimationDuration() {
        return EXPAND_DURATION;
    }

    public int getGravity() {
        return mGravity;
    }

    public void setHorizontalReverseExit(boolean isHorizontalReverseExit){
        mHorizontalReverseExit = isHorizontalReverseExit;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            cancel();
        }
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                float x = event.getRawX();
                float y = event.getRawY();
                mLastMotionY = y;
                mLastMotionX = x;
                break;
            case MotionEvent.ACTION_MOVE:
                float deltaY = event.getRawY() - mLastMotionY;
                float deltaX = event.getRawX() - mLastMotionX;
                x = event.getX();
                y = event.getY();
                if (deltaY != 0 && canScrollVertically(this, false, deltaY, x, y)) {
                    return false;
                }
                if (mGravity == Gravity.BOTTOM && Math.abs(deltaY) > Math.abs(deltaX)) {
                    if (deltaY > 0) {
                        if (!bottomCanSlidingDown()) {
                            return false;
                        }
                    } else {
                        if (!bottomCanSlidingUp()) {
                            return false;
                        }
                    }
                }
                if (((mGravity == Gravity.TOP || mGravity == Gravity.BOTTOM) && Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > mTouchSlop)
                        ||
                        ((mGravity == Gravity.LEFT || mGravity == Gravity.RIGHT) && Math.abs(deltaX) > Math.abs(deltaY)
                                && Math.abs(deltaX) > mTouchSlop)) {
                    mLastMotionY = event.getRawY();
                    mLastMotionX = event.getRawX();
                    dragTo(deltaX, deltaY, mHorizontalReverseExit);
                    mTouchState = TOUCH_STATE_SCROLLING;
                    return true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                releaseViewForPointerUp(0, 0, mHorizontalReverseExit);
                cancel();
                break;
        }
        return super.onInterceptTouchEvent(event);
    }

    public boolean bottomCanSlidingUp() {
        return true;
    }

    public boolean bottomCanSlidingDown() {
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mTranslationFinished) {
            return true;
        }
        final int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            cancel();
        }
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                final float x = event.getRawX();
                final float y = event.getRawY();
                mLastMotionY = y;
                mLastMotionX = x;
                if (isCollapseEdgesTouched((int) x, (int) y)) {
                    mTouchState = TOUCH_STATE_SCROLLING;
                    return true;
                } else {
                    mTouchState = TOUCH_STATE_REST;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mTouchState == TOUCH_STATE_SCROLLING) {
                    float deltaY = event.getRawY() - mLastMotionY;
                    float deltaX = event.getRawX() - mLastMotionX;
                    mLastMotionY = event.getRawY();
                    mLastMotionX = event.getRawX();
                    if (mGravity == Gravity.BOTTOM && Math.abs(deltaY) > Math.abs(deltaX)) {
                        if (deltaY > 0) {
                            if (!bottomCanSlidingDown()) {
                                mTouchState = TOUCH_STATE_REST;
                                return false;
                            }
                        } else {
                            if (!bottomCanSlidingUp()) {
                                mTouchState = TOUCH_STATE_REST;
                                return false;
                            }
                        }
                    }

                    dragTo(deltaX, deltaY,  mHorizontalReverseExit);
                    return true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (mTouchState != TOUCH_STATE_REST) {
                    releaseViewForPointerUp(0, 0, mHorizontalReverseExit);
                }
                cancel();
                break;
        }
        super.onTouchEvent(event);
        return true;
    }

    public boolean isTranslationFinished() {
        return mTranslationFinished;
    }

    public void dragTo(float dx, float dy, boolean horizontalReverseExit) {
        if (!mTranslationFinished) {
            return;
        }
        Log.e("zhu","dx=" + dx);
        if (mGravity == Gravity.LEFT) {
            int translationX = (int)(getTranslationX() + dx);
            setTranslationX(horizontalReverseExit ? translationX : clamp(translationX, -getWidth(), 0));
        } else if (mGravity == Gravity.RIGHT) {
            int translationX = (int)(getTranslationX() + dx);
            setTranslationX(horizontalReverseExit ? translationX : clamp(translationX, 0, getWidth()));
        } else if (mGravity == Gravity.TOP) {
            int text = clamp((int) (getTranslationY() + dy), -getHeight(), 0);
            setTranslationY(text);
        } else if (mGravity == Gravity.BOTTOM) {
            int text = clamp((int) (getTranslationY() + dy), 0, getHeight());
            setTranslationY(text);
        }
        dispatchOnSlide();
    }

    public static int clamp(int x, int min, int max) {
        return Math.min(max, Math.max(min, x));
    }

    private boolean isCollapseEdgesTouched(int x, int y) {
        if (mGravity != Gravity.NO_GRAVITY) {
            if ((y > getBottom() - mCollapseEdgeSize && mGravity == Gravity.TOP)
                    || (y < getTop() + mCollapseEdgeSize && mGravity == Gravity.BOTTOM)
                    || (x > getRight() - mCollapseEdgeSize && mGravity == Gravity.LEFT)
                    || (x < getLeft() + mCollapseEdgeSize && mGravity == Gravity.RIGHT)) {
                return true;
            }
        }
        return false;
    }

    public boolean isExpandEdgesTouched(int x, int y) {
        if (mGravity != Gravity.NO_GRAVITY) {
            if ((y < mExpandEdgeSize && mGravity == Gravity.TOP)
                    || (y > getBottom() - mExpandEdgeSize && mGravity == Gravity.BOTTOM)
                    || (x < mExpandEdgeSize && mGravity == Gravity.LEFT)
                    || (x > getRight() - mExpandEdgeSize && mGravity == Gravity.RIGHT)) {
                return true;
            }
        }
        return false;
    }

    public void expand(boolean animation) {
        float velocity = getExpandMinVelocity() + 1;
        if (mGravity == Gravity.RIGHT) {
            velocity = -velocity;
        }
        if (animation) {
            releaseViewForPointerUp(velocity, velocity, false);
        } else {
            if (!mIsExpanded) {
                mIsExpanded = true;
                setViewTranslation(0);
                dispatchOnExpaned();
            }
        }
    }

    public void collapsed(boolean animation) {
        collapsed(animation, AUTOMATIC_COLLAPSE_DURATION);
    }

    protected void collapsed(boolean animation, long duration) {
        if (animation) {
            if (!mIsExpanded) {
                return;
            }

            if (!mTranslationFinished) {
                return;
            }
            mNextExpandedStatus = false;
            playTranslationAnimation(duration, false);
        } else {
            if (mIsExpanded) {
                mIsExpanded = false;
                setViewTranslation(getInitialTranslation());
                dispatchOnCollapsed();
            }
        }
    }

    public void collapsedWithAlphaAnimation() {
        if (!mIsExpanded) {
            return;
        }

        if (!mTranslationFinished) {
            return;
        }
        mNextExpandedStatus = false;
        mTranslationFinished = false;
        ObjectAnimator transAnimation = ObjectAnimator.ofFloat(this, "alpha", 1.0f, 0f);
        transAnimation.setDuration(AUTOMATIC_COLLAPSE_DURATION);
        transAnimation.setInterpolator(new AccelerateInterpolator());
        transAnimation.addListener(mAlphaAnimatorListener);
        transAnimation.start();
    }

    public void releaseViewForPointerUp(float xvel, float yvel, boolean horizontalReverseExit) {
        if (!mTranslationFinished) {
            return;
        }
        mNextExpandedStatus = canExpanded(xvel, yvel, horizontalReverseExit);
        playTranslationAnimation(getExpandAnimationDuration(), horizontalReverseExit);
    }

    private void playTranslationAnimation(long duration, boolean horizontalReverseExit) {
        float startTrans = getCurrentTranslation();
        float endTrans = mNextExpandedStatus ? 0 : getInitialTranslation();
        if(horizontalReverseExit && !mNextExpandedStatus){
            if(startTrans > getWidth() * COLLAPSE_THRESHOLD){
                endTrans = getWidth();
            }else{
                endTrans = -getWidth();
            }
        }
        if (startTrans != endTrans) {
            mTranslationFinished = false;
            ObjectAnimator transAnimation = ObjectAnimator.ofFloat(this, "viewTranslation", startTrans, endTrans);
            transAnimation.setDuration(duration);
            transAnimation.setInterpolator(new DecelerateInterpolator());
            transAnimation.addListener(mAnimatorListener);
            transAnimation.start();
        } else {
            updateExpandStatus();
        }
    }

    private AnimatorListener mAlphaAnimatorListener = new AnimatorListener() {

        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            if(!mNextExpandedStatus) {
                setViewTranslation(getInitialTranslation());
            }
            updateExpandStatus();
            mTranslationFinished = true;
            ExpandView.this.setAlpha(1f);
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            mTranslationFinished = true;
            ExpandView.this.setAlpha(1f);
        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };

    private AnimatorListener mAnimatorListener = new AnimatorListener() {

        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            if(!mNextExpandedStatus) {
                setViewTranslation(getInitialTranslation());
            }
            updateExpandStatus();
            mTranslationFinished = true;
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            if(!mNextExpandedStatus) {
                setViewTranslation(getInitialTranslation());
            }
            updateExpandStatus();
            mTranslationFinished = true;
        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };

    private void updateExpandStatus() {
        if (mNextExpandedStatus != mIsExpanded) {
            mIsExpanded = mNextExpandedStatus;
            if (mIsExpanded) {
                dispatchOnExpaned();
            } else {
                dispatchOnCollapsed();
            }
        }
    }

    public void setViewTranslation(float value) {
        if (mGravity == Gravity.LEFT || mGravity == Gravity.RIGHT) {
            setTranslationX(value);
        } else if (mGravity == Gravity.TOP || mGravity == Gravity.BOTTOM) {
            setTranslationY(value);
        }
        dispatchOnSlide();
    }

    private float getCurrentTranslation() {
        if (mGravity == Gravity.LEFT || mGravity == Gravity.RIGHT) {
            return getTranslationX();
        } else if (mGravity == Gravity.TOP || mGravity == Gravity.BOTTOM) {
            return getTranslationY();
        }
        return 0;
    }

    private float getInitialTranslation() {
        if (mGravity == Gravity.LEFT) {
            return -getWidth();
        } else if (mGravity == Gravity.RIGHT) {
            return getWidth();
        } else if (mGravity == Gravity.TOP) {
            return -getHeight();
        } else if (mGravity == Gravity.BOTTOM) {
            return getHeight();
        }
        return 0;
    }

    private boolean canExpanded(float xvel, float yvel, boolean horizontalReverseExit) {
        if (!mIsExpanded) {
            if ((mGravity == Gravity.LEFT && (getTranslationX() > -getWidth() * EXPAND_THRESHOLD))
                    || (mGravity == Gravity.RIGHT && (getTranslationX() < getWidth() * EXPAND_THRESHOLD))
                    || (mGravity == Gravity.BOTTOM && (getTranslationY() < getHeight() * EXPAND_THRESHOLD))
                    || (mGravity == Gravity.TOP && (getTranslationY() > -getHeight() * EXPAND_THRESHOLD))) {
                return true;
            }
            if (xvel != 0 || yvel != 0) {
                if ((mGravity == Gravity.LEFT && (xvel > getExpandMinVelocity()))
                        || (mGravity == Gravity.RIGHT && (xvel < 0 && (Math.abs(xvel) > getExpandMinVelocity())))
                        || (mGravity == Gravity.BOTTOM && (yvel < 0 && (Math.abs(yvel) > getExpandMinVelocity())))
                        || (mGravity == Gravity.TOP && (yvel > getExpandMinVelocity()))) {
                    return true;
                }
            }
        } else {
            if(horizontalReverseExit){
                if((mGravity == Gravity.LEFT || mGravity == Gravity.RIGHT) &&
                        ((Math.abs(getTranslationX()) > getWidth() * COLLAPSE_THRESHOLD) || Math.abs(xvel) > getExpandMinVelocity())){
                    return false;
                }
            }
            if ((mGravity == Gravity.LEFT && (getTranslationX() > -getWidth() * COLLAPSE_THRESHOLD))
                    || (mGravity == Gravity.RIGHT && (getTranslationX() < getWidth() * COLLAPSE_THRESHOLD))
                    || (mGravity == Gravity.BOTTOM && (getTranslationY() < getHeight() * COLLAPSE_THRESHOLD))
                    || (mGravity == Gravity.TOP && (getTranslationY() > -getHeight() * COLLAPSE_THRESHOLD))) {
                return true;
            }
            if (xvel != 0 || yvel != 0) {
                if ((mGravity == Gravity.LEFT && (xvel >= 0 || (Math.abs(xvel) < getExpandMinVelocity())))
                        || (mGravity == Gravity.RIGHT && (xvel <= 0 || xvel < getExpandMinVelocity()))
                        || (mGravity == Gravity.BOTTOM && (yvel <= 0 || yvel < getExpandMinVelocity()))
                        || (mGravity == Gravity.TOP && (yvel >= 0 || (Math.abs(xvel) < getExpandMinVelocity())))) {
                    return true;
                }
            }
        }
        return false;
    }

    private void cancel() {
        mTouchState = TOUCH_STATE_REST;
        mLastMotionX = 0f;
        mLastMotionY = 0f;
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private void dispatchOnExpaned() {
        if (mExpandListener != null) {
            mExpandListener.onExpaned(this);
        }
    }

    private void dispatchOnCollapsed() {
        if (mExpandListener != null) {
            mExpandListener.onCollapsed(this);
        }
    }

    private void dispatchOnSlide() {
        float slideOffset = 0f;
        if (mGravity == Gravity.LEFT || mGravity == Gravity.RIGHT) {
            slideOffset = 1f - Math.abs(getTranslationX()) / getWidth();
        } else if (mGravity == Gravity.TOP) {
            slideOffset = 1f - getTranslationY() / -getHeight();
        } else if (mGravity == Gravity.BOTTOM) {
            slideOffset = 1f - getTranslationY() / getHeight();
        }
        if (mExpandListener != null) {
            mExpandListener.onSlide(this, slideOffset);
        }
    }

    @Override
    public void onLayoutChange(View paramView, int l1, int t1, int r1, int b1, int l2, int t2, int r2, int b2) {
        if (mGravity == Gravity.LEFT) {
            int w = r1 - l1;
            setTranslationX(-w);
        } else if (mGravity == Gravity.RIGHT) {
            int w = r1 - l1;
            setTranslationX(w);
        } else if (mGravity == Gravity.TOP) {
            int h = b1 - t1;
            setTranslationY(-h);
        } else if (mGravity == Gravity.BOTTOM) {
            int h = b1 - t1;
            setTranslationY(h);
        }
        removeOnLayoutChangeListener(this);
    }

    public boolean isExpand() {
        return mIsExpanded;
    }

    public interface ExpandListener {
        public void onSlide(View expandView, float slideOffset);

        public void onExpaned(View expandView);

        public void onCollapsed(View expandView);
    }

    public void updateBackgroundIfNeeded() {
    }

    protected boolean canScrollVertically(View v, boolean checkV, float dy, float x, float y) {
        return false;
    }

}
